/*!
 * @file areaSquare.cpp
 * @brief Ciało areaSquare.hh
 */

#include "modes/world/area/areaSquare.hh"
using namespace std;

/*--------------------------------------------------------------------------*/

areaSquare::areaSquare(int startx, int starty, int stopx, int stopy,
		float *heightmap, int totalX, int totalY, bool isMainContent) :
	startx(startx), starty(starty), stopx(stopx), stopy(stopy), totalX(totalX),
			totalY(totalY), ssidex(stopx - startx), ssidey(stopy - starty),
			vertexCount(), allowQuadTree(isMainContent) {

	glGenVertexArrays(1, &this->vao);
	glGenBuffers(3, this->vbo);

	stringstream ss;
	ss << this->ssidex << ":" << this->ssidey << " " << startx << ":" << starty
			<< " do " << stopx << ":" << stopy << " spośród " << totalX << ":"
			<< totalY;

	pawirr::graphic->loadingScreen("Wczytawnie sektorów... " + ss.str());

	/*
	 * ustawianie zmiennych obiektu
	 */

	this->heightmap = heightmap;

	this->lod = this->ssidex / 64 * 2;

	if (this->lod > this->totalX) {
		this->lod = this->totalX;
	}
	//QUAD - dla triangle powinno być 6 zamiast 4
	const_cast<long int &> (this->vertexCount) = (int) (this->ssidex
			* this->ssidey * 4) / (this->lod * this->lod);

	/*
	 * Wypełnianie VAO i VBO
	 */
	glBindVertexArray(this->vao);

	this->terrainShader = new shader("terrainShader");
	this->terrainShader->addShader("pawirr/shaders/terrain.v", ST_V);
	this->terrainShader->addShader("pawirr/shaders/terrain.f", ST_F);

	pawirr::graphic->_env->addEnvUniforms(this->terrainShader);

	this->fillVerticles(0);
	this->fillNormals(1);
	this->fillTexcoords(2);

	this->terrainShader->addAttrib("attPos", 0);
	this->terrainShader->addAttrib("attNorm", 1);
	this->terrainShader->addAttrib("attTex", 2);

	this->terrainShader->addUniformf("terrain_translation",
			&this->translation.x, &this->translation.y);

	if (isMainContent) {
		this->terrainShader->addTexture("tex_map",
				pawirr::m->world->_area->texturePack->texture_map);
	} else {
		this->terrainShader->addTexture("tex_map",
				pawirr::m->world->_area->texturePack->texture_mapES);
	}

	this->terrainShader->addTexture("tex_r",
			pawirr::m->world->_area->texturePack->texture_r);
	this->terrainShader->addTexture("tex_g",
			pawirr::m->world->_area->texturePack->texture_g);
	this->terrainShader->addTexture("tex_b",
			pawirr::m->world->_area->texturePack->texture_b);
	this->terrainShader->addTexture("tex_detail",
			pawirr::m->world->_area->texturePack->texture_detail);

	this->terrainShader->linkProgram();

	glBindVertexArray(0);

	this->centerOfSquare = point2f(this->startx + this->ssidex / 2,
			this->starty + this->ssidey / 2);

	if (this->ssidex > 64) {

		this->terrA = new areaSquare(this->startx, this->starty, this->startx
				+ this->ssidex / 2, this->starty + this->ssidey / 2,

		this->heightmap, this->totalX, this->totalY, true);

		this->terrB = new areaSquare(this->startx + this->ssidex / 2,
				this->starty, this->startx + this->ssidex, this->starty
						+ this->ssidey / 2,

				this->heightmap, this->totalX, this->totalY, true);

		this->terrC = new areaSquare(this->startx + this->ssidex / 2,
				this->starty + this->ssidey / 2, this->startx + this->ssidex,
				this->starty + this->ssidey,

				this->heightmap, this->totalX, this->totalY, true);

		this->terrD = new areaSquare(this->startx, this->starty + this->ssidey
				/ 2, this->startx + this->ssidex / 2, this->starty
				+ this->ssidey,

		this->heightmap, this->totalX, this->totalY, true);

	} else {
		this->terrA = NULL;
		this->terrB = NULL;
		this->terrC = NULL;
		this->terrD = NULL;
	}
}

/*--------------------------------------------------------------------------*/

void areaSquare::draw(int maxDetails, bool useShader, point2f translate) {

	this->translation = translate;

	/*
	 * Czy przypadkiem środek obszaru nie jest
	 * dalej niż zakres widzenia * długość kwadrata do namalowania
	 */
	if ((pawirr::graphic->_env->cameraPos.xz() - (this->centerOfSquare
			+ this->translation)).length() > pawirr::graphic->_env->visualRange
			+ (float) this->ssidex) {
		return;
	}

	/*
	 * Czy rysować szczegółowo ?
	 */
	if (pawirr::graphic->_env->cameraPos.x + 100.0f > this->startx
			&& pawirr::graphic->_env->cameraPos.x - 100.0f< this->stopx
			&& pawirr::graphic->_env->cameraPos.z + 100.0f> this->starty
			&& pawirr::graphic->_env->cameraPos.z - 100.0f< this->stopy) {
		if (maxDetails > 0 && this->terrA != NULL) {
			this->terrA->draw(maxDetails - 1, useShader);
			this->terrB->draw(maxDetails - 1, useShader);
			this->terrC->draw(maxDetails - 1, useShader);
			this->terrD->draw(maxDetails - 1, useShader);

			//obszar namalowano dokładniej.
			return;
		}
	}

	if (useShader)
		this->terrainShader->bind();
	else {
		glColor3f(0.0, 0.0, 0.0);
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
	}

	glPushMatrix();
	glBindVertexArray(this->vao);

	glTranslatef(this->translation.x, -pawirr::graphic->_env->waterLevel,
			this->translation.y);

	//QUAD - powinni być GL_TRIANGLES
	glDrawArrays(GL_QUADS, 0, this->vertexCount);
	/*-----------------------------*/
	/*
	 * sprzątanie
	 */
	glActiveTexture(GL_TEXTURE0);
	glBindVertexArray(0);
	glPopMatrix();

	if (useShader)
		this->terrainShader->unbind();
	else {
		glEnable(GL_LIGHTING);
		glEnable(GL_DEPTH_TEST);
	}
}

/*--------------------------------------------------------------------------*/

void areaSquare::fillVerticles(int id) {
	point3f *vhVertices = new point3f[this->vertexCount];

	int nIndex = 0;
	float flX;
	float flZ;

	for (int hMapX = this->startx; hMapX < this->startx + this->ssidex; hMapX
			+= this->lod) {
		for (int hMapZ = this->starty; hMapZ < this->starty + this->ssidey; hMapZ
				+= this->lod) {

			for (int nTri = 0; nTri < 6; nTri++) {
				//QUAD
				if (nTri == 3 || nTri == 4)
					continue;

				flX = (float) hMapX
						+ ((nTri == 1 || nTri == 2 || nTri == 4) ? this->lod
								: 0);
				flZ = (float) hMapZ
						+ ((nTri == 2 || nTri == 4 || nTri == 5) ? this->lod
								: 0);

				vhVertices[nIndex].x = flX;
				int id = (int) flX + (int) flZ * (int) this->totalX;

				if (this->heightmap != NULL && id < this->totalX * this->totalY) {
					vhVertices[nIndex].y = this->heightmap[id];

				} else {

					vhVertices[nIndex].y = 0;

				}

				vhVertices[nIndex].z = flZ;

				nIndex++;
			}//for dla 6 wierzcholkow

		}//for dla z
	}//for dla x

	glBindBufferARB(GL_ARRAY_BUFFER, this->vbo[id]);
	glBufferDataARB(GL_ARRAY_BUFFER_ARB, this->vertexCount * sizeof(point3f),
			vhVertices, GL_STATIC_DRAW_ARB);
	glEnableVertexAttribArray(id);
	glVertexAttribPointer(id, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

	delete[] vhVertices;
}

/*--------------------------------------------------------------------------*/

void areaSquare::fillTexcoords(int id) {
	point2f *vhTexCoords = new point2f[this->vertexCount];

	int nIndex = 0;
	float flX;
	float flZ;

	for (int hMapX = this->startx; hMapX < this->startx + this->ssidex; hMapX
			+= this->lod) {
		for (int hMapZ = this->starty; hMapZ < this->starty + this->ssidey; hMapZ
				+= this->lod) {
			for (int nTri = 0; nTri < 6; nTri++) {

				//QUAD
				if (nTri == 3 || nTri == 4)
					continue;

				flX = (float) hMapX
						+ ((nTri == 1 || nTri == 2 || nTri == 4) ? this->lod
								: 0);
				flZ = (float) hMapZ
						+ ((nTri == 2 || nTri == 4 || nTri == 5) ? this->lod
								: 0);

				vhTexCoords[nIndex].x = flX / (float) this->totalX;

				vhTexCoords[nIndex].y = flZ / (float) this->totalY;

				nIndex++;
			}//for dla 6 wierzcholkow

		}//for dla z
	}//for dla x

	glBindBufferARB(GL_ARRAY_BUFFER, this->vbo[id]);
	glBufferDataARB(GL_ARRAY_BUFFER_ARB, this->vertexCount * sizeof(point2f),
			vhTexCoords, GL_STATIC_DRAW_ARB);
	glEnableVertexAttribArray(id);
	glVertexAttribPointer(id, 2, GL_FLOAT, GL_FALSE, sizeof(point2f), NULL);

	delete[] vhTexCoords;
}

/*--------------------------------------------------------------------------*/

void areaSquare::fillNormals(int id) {

	if (this->heightmap == NULL) {
		point3f *vhNormals = new point3f[this->vertexCount];

		for (int i = 0; i < this->vertexCount; i++) {
			vhNormals[i] = point3f(0.0f, 1.0f, 0.0f);
		}

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, this->vbo[id]);
		glBufferDataARB(GL_ARRAY_BUFFER_ARB, this->vertexCount
				* sizeof(point3f), vhNormals, GL_STATIC_DRAW_ARB);
		glEnableVertexAttribArray(id);
		glVertexAttribPointer(id, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

		delete[] vhNormals;
		return;
	}

	/*
	 * Oblcizanie i uśrednianie wektorów normalnych [8 sztuk]
	 */
	//cout << "normale" << endl;
	point3f *nvectors = new point3f[(this->ssidex + 1) * (this->ssidey + 1)];

	for (int X = this->startx; X < this->startx + this->ssidex + 1; X
			+= this->lod) {
		for (int Y = this->starty; Y < this->starty + this->ssidey + 1; Y
				+= this->lod) {

			point3f last_vector;
			point3f vector_sum;

			for (int iter = 0; iter < 8; iter++) {
				int dx, dy;

				switch (iter) {
				case 0:
					dx = -this->lod;
					dy = this->lod;
					break;
				case 1:
					dx = 0;
					dy = this->lod;
					break;
				case 2:
					dx = this->lod;
					dy = this->lod;
					break;
				case 3:
					dx = this->lod;
					dy = 0;
					break;
				case 4:
					dx = this->lod;
					dy = -this->lod;
					break;
				case 5:
					dx = 0;
					dy = -this->lod;
					break;
				case 6:
					dx = -this->lod;
					dy = -this->lod;
					break;
				case 7:
					dx = -this->lod;
					dy = 0;
					break;
				}

				/*
				 * sprawdzenie czy współrzędne końca wektora mieszczą się w tablicy
				 */
				int id = (X + dx) + (Y + dy) * (this->totalX);
				int id_center = (X) + (Y) * (this->totalX);

				if (id < 0 || id >= this->totalX * this->totalY) {
					continue;
				}
				if (id_center < 0 || id_center >= this->totalX * this->totalY) {
					continue;
				}

				/*
				 * Obliczanie wektora (zewnętrze - środek)
				 */
				point3f current_vector(dx, this->heightmap[id]
						- this->heightmap[id_center], dy);

				if (last_vector.length() > 0.1) {

					point3f normalvec(last_vector.y * current_vector.z
							- last_vector.z * current_vector.y, last_vector.z
							* current_vector.x - last_vector.x
							* current_vector.z, last_vector.x
							* current_vector.y - last_vector.y
							* current_vector.x);

					normalvec.normalize();
					float test = normalvec.length();
					if (test > 0.95 && test < 1.05) {
						vector_sum = vector_sum + normalvec;
						last_vector = current_vector;
					}
				} else {
					last_vector = current_vector;
					last_vector.normalize();
				}
			}
			vector_sum.normalize();

			int id = (X - this->startx) + (Y - this->starty) * (this->ssidex);

			if (id < 0) {
				id = 0;
				cout << "haha" << endl;
			}

			if (id > (this->ssidex + 1) * (this->ssidey + 1)) {
				id = 0;
				cout << "hehe" << endl;
			}

			nvectors[id] = vector_sum;
			//cout << nvectors[(X) + (Y) * (this->sizeX)];
		}//X
	}//Y

	//cout << "prawie" << endl;
	/*
	 * Tworzenie i wypełnianie tablicy wektorami
	 */
	point3f *vhNormals = new point3f[this->vertexCount];

	//todo błąd w wektorach normalnych = ciapiate obwódki
	int nIndex = 0;
	for (int hMapX = 0; hMapX < this->ssidex; hMapX += this->lod) {
		for (int hMapY = 0; hMapY < this->ssidey; hMapY += this->lod) {

			vhNormals[nIndex++] = nvectors[hMapX + hMapY * this->ssidex];

			vhNormals[nIndex++] = nvectors[hMapX + this->lod + hMapY
					* this->ssidex];

			vhNormals[nIndex++] = nvectors[hMapX + this->lod + (hMapY
					+ this->lod) * this->ssidex];

			vhNormals[nIndex++] = nvectors[hMapX + (hMapY + this->lod)
					* this->ssidex];
		}
	}

	glBindBufferARB(GL_ARRAY_BUFFER_ARB, this->vbo[id]);
	glBufferDataARB(GL_ARRAY_BUFFER_ARB, this->vertexCount * sizeof(point3f),
			vhNormals, GL_STATIC_DRAW_ARB);

	glEnableVertexAttribArray(id);
	glVertexAttribPointer(id, 3, GL_FLOAT, GL_FALSE, sizeof(point3f), NULL);

	delete[] nvectors;
	delete[] vhNormals;

	//cout << "normaledone" << endl;

}

/*--------------------------------------------------------------------------*/
